Skip to content

feat(crypto)!: track leanVM main (internalized XMSS + reworked aggregation)#539

Open
MegaRedHand wants to merge 3 commits into
mainfrom
build/leanvm-track-main
Open

feat(crypto)!: track leanVM main (internalized XMSS + reworked aggregation)#539
MegaRedHand wants to merge 3 commits into
mainfrom
build/leanvm-track-main

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

What

Points the leanVM dependency at the main branch, as requested.

leanVM's main ("Xmss api rework") removed the lean-multisig and leansig_wrapper crates ethlambda imported, internalized XMSS in its own xmss crate (dropping the external leanSig dependency), and reworked the recursive-aggregation API. So tracking main is a migration of the whole signature stack, not a version bump — cargo update fails outright (no matching package named leansig_wrapper).

Changes

Area Before (rev e2592df) After (main)
Deps lean-multisig, leansig_wrapper, leansig (devnet4) rec_aggregation, xmss + ssz (ethereum_ssz), postcard
Key/sig types leanSig devnet4 leanVM's own xmss crate
Proof wire form compress_without_pubkeys(), pubkeys attached at decode to_bytes()/from_bytes() (postcard), pubkeys embedded
Pubkey size 52 bytes 32 bytes (PUB_KEY_SSZ_LEN)
Prover init setup_prover / setup_verifier idempotent init_aggregation_bytecode
Secret-key window sliding two-tree preparation window fixed activation range (advance_preparation is now a no-op)
  • types::signature: rebuilt ValidatorPublicKey/ValidatorSignature/ValidatorSecretKey on xmss. Pubkeys/sigs (de)serialize via SSZ; secret keys via postcard. SIGNATURE_SIZE + new PUBLIC_KEY_SIZE come from the scheme constants. Added ValidatorSecretKey::public_key() (now that xmss exposes it).
  • crypto: rewired aggregation/verification against rec_aggregation. Since proofs now embed participant pubkeys, the verify paths cross-check the embedded set against the caller's expected validators instead of attaching pubkeys at decode. split_type_2_by_message drops its now-redundant pubkey parameter.
  • Validator pubkeys 52 → 32 bytes: genesis/state layouts, deser messages, and test fixtures updated; pinned genesis state/block roots recomputed.

Validation

  • cargo build/check --workspace --all-targets
  • make lint (clippy -D warnings)
  • ✅ all lib + bin unit tests pass
  • ⚠️ Heavy real-crypto tests remain #[ignore] (leanVM proving needs a release-sized stack; init_aggregation_bytecode overflows the debug stack).

⚠️ Breaking / follow-ups

This changes the on-wire signature/proof formats and the genesis key format:

  • Interop-incompatible with clients still on the previous scheme.
  • Requires regenerated genesis keys (new 32-byte xmss pubkey format) — external tooling (lean-quickstart).
  • Fixture-driven spec tests (forkchoice/signature/stf/ssz) need regenerated fixtures; current released fixtures are the old format.

Landing this needs ecosystem coordination; it is not a drop-in for the live devnet.

…ation)

Point the leanVM dependency at `main`. leanVM's `main` ("Xmss api rework")
removed the `lean-multisig` and `leansig_wrapper` crates ethlambda imported,
internalized XMSS in its own `xmss` crate (dropping the external leanSig
dependency), and reworked the recursive-aggregation API. Tracking main is
therefore a migration of the whole signature stack, not a version bump.

Changes:
- Cargo: replace `leansig`/`lean-multisig`/`leansig_wrapper` with `xmss` +
  `rec_aggregation` (git, branch=main), plus `ssz` (ethereum_ssz, used by the
  xmss SSZ impls) and `postcard` (secret-key format).
- types::signature: rebuild ValidatorPublicKey/Signature/SecretKey on leanVM's
  `xmss` crate. Keys/signatures (de)serialize via SSZ (`PUB_KEY_SSZ_LEN` /
  `SIGNATURE_SSZ_LEN`); secret keys via postcard. `SIGNATURE_SIZE` and the new
  `PUBLIC_KEY_SIZE` are sourced from the xmss scheme constants. The sliding
  preparation-window API becomes a fixed activation range (`advance_preparation`
  is now a no-op; keys warm their signing cache on demand).
- crypto: rewrite aggregation/verification against `rec_aggregation`. Proofs now
  serialize with `to_bytes()`/`from_bytes()` and embed participant pubkeys, so
  the verify paths cross-check the embedded set against the expected validators
  instead of attaching them at decode time. `setup_prover`/`setup_verifier` are
  replaced by the idempotent `init_aggregation_bytecode`.
- Validator public keys are now 32 bytes (was 52); genesis/state layouts and
  fixtures updated accordingly.

BREAKING: this changes the on-wire signature/proof formats and the genesis key
format. It is interop-incompatible with clients still on the previous scheme and
requires regenerated genesis keys; the fixture-driven spec tests need
regenerated fixtures. Landing this needs ecosystem coordination.
@github-actions

Copy link
Copy Markdown

🤖 Kimi Code Review

Security & Correctness

  1. Dependency Pinning (Critical)
    Cargo.toml tracks leanVM dependencies by branch = "main" rather than a specific rev. While Cargo.lock currently pins commit a73ab114, this is fragile—accidental lockfile updates could pull in untested VM changes.
    Suggestion: Pin to a specific git revision in Cargo.toml for reproducibility:

    xmss = { git = "https://github.com/leanEthereum/leanVM.git", rev = "a73ab114..." }
  2. Canonical Pubkey Ordering
    crates/common/crypto/src/lib.rs:113-121 (sorted_dedup_pubkeys) sorts and deduplicates pubkeys before comparison. This assumes leanVM’s aggregation prover applies the exact same canonicalization when embedding info.pubkeys in the proof.
    Action: Verify that leanVM’s rec_aggregation crate sorts pubkeys with the same Ord implementation (standard lexicographic on the underlying bytes). A mismatch here would cause valid proofs to fail verification.

  3. Overflow in Slot Range
    crates/common/types/src/signature.rs:157

    (*range.start() as u64)..(*range.end() as u64 + 1)

    If range.end() is u32::MAX, the addition overflows (panic in debug, wrap in release). While Ethereum slots won’t reach this in practice, consider using checked_add or RangeInclusive to be defensive.

  4. Zero-Signature Safety
    crates/common/types/src/attestation.rs:66-68 defines blank_xmss_signature() as all-zero bytes. The comment states this decodes as “structurally valid (but unverifiable)”. Ensure leanVM’s XmssSignature SSZ deserialization rejects signatures with all-zero WOTS+ values during verify, otherwise a malicious genesis/config could inject a seemingly valid but trivially forgeable signature.
    File: crates/common/types/src/attestation.rs:66

Performance & Memory

  1. Allocation in to_bytes
    crates/common/types/src/signature.rs:53 and 61 allocate Vec<u8> for every serialization. For hot paths (e.g., signing thousands of attestations), consider exposing ssz::Encode directly to callers to allow stack-allocated buffers or zero-copy serialization where possible.

  2. Lazy Initialization
    The replacement of std::sync::Once with leanVM’s internal OnceLock (init_aggregation_bytecode) is idiomatic and safer. The #[ignore = "slow"] tests correctly gate the expensive bytecode compilation.

Code Quality & Maintainability

  1. Error Type Erasure
    crates/common/crypto/src/lib.rs:304 and 469 convert leanVM errors to strings:

    .map_err(|err| VerificationError::VerificationFailed(format!("{err:?}")))?;

    This loses structured error information. If leanVM exposes specific error enums (e.g., InvalidProof, SlotMismatch), map them explicitly to VerificationError variants for better diagnostics.

  2. Consistent Constant Usage
    Good: PUBLIC_KEY_SIZE is now sourced from xmss::PUB_KEY_SSZ_LEN (crates/common/types/src/signature.rs:28).
    Nit: Ensure SIGNATURE_SIZE is used everywhere instead of magic numbers (currently consistent across the diff).

  3. Doc Comment Accuracy
    crates/common/crypto/src/lib.rs:482-484 mentions “the caller supplies the expected message” but the function signature no longer takes pubkeys_per_component (removed in this PR). Update the doc to reflect that pubkeys are now embedded in the proof.

Testing

  1. Genesis State Root Update
    The hardcoded state root in crates/common/types/src/genesis.rs:152 changed due to the 52→32 byte pubkey resize. Confirm that this new root matches the output of an independent leanVM-based genesis generator to prevent consensus split on the devnet.

  2. Dummy Signature Robustness
    Tests in crates/storage/src/store.rs:2487 and crates/blockchain/src/aggregation.rs:794 now use all-zero signatures. Add a comment or assertion ensuring these are never used in verification paths (only deserialization), as they would fail actual XMSS verification.

Summary

The migration from the external leanSig crate to leanVM’s internalized xmss/rec_aggregation is well-executed. The shift to self-contained proofs (pubkeys embedded) is architecturally superior. Address the dependency pinning (Item 1) and verify the pubkey sorting canonicalization (Item 2) before merging.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Migrates the validator signature stack to leanVM’s internal XMSS and recursive-aggregation APIs.

  • Replaces leanSig wrappers with XMSS SSZ key/signature encoding and postcard secret-key encoding.
  • Embeds participant public keys in aggregation proofs and cross-checks them during verification.
  • Updates validator key sizes, genesis/state layouts, storage fixtures, and pinned roots from 52 to 32 bytes.
  • Adapts block aggregation, proof splitting, reaggregation, and prover initialization to the new leanVM APIs.

Confidence Score: 5/5

The PR appears safe to merge from the reviewed code paths, with no concrete changed-code defect identified.

Verification still binds each aggregate component to its expected message, slot, and validator key set, while the storage and consensus layouts are consistently migrated to the new XMSS encoding.

Important Files Changed

Filename Overview
crates/common/crypto/src/lib.rs Migrates aggregation, verification, serialization, and proof splitting to rec_aggregation while binding embedded public-key sets to caller expectations.
crates/common/types/src/signature.rs Rebuilds validator key and signature wrappers around leanVM XMSS with SSZ wire encoding and postcard secret-key persistence.
crates/common/types/src/state.rs Changes validator public-key fields to the new 32-byte XMSS encoding, thereby updating the consensus state layout.
crates/common/types/src/attestation.rs Updates the fixed XMSS signature representation and simplifies the structurally valid blank-signature construction.
crates/blockchain/src/reaggregate.rs Adapts block-proof splitting to proofs that carry their participant public keys internally.
Cargo.toml Replaces the removed leanSig and lean-multisig dependencies with leanVM XMSS, recursive aggregation, SSZ, and postcard dependencies.

Sequence Diagram

sequenceDiagram
    participant Validator
    participant XMSS
    participant Aggregator
    participant Block
    participant Verifier
    Validator->>XMSS: Sign message at slot
    XMSS-->>Aggregator: SSZ signature + public key
    Aggregator->>Aggregator: Build Type-1 proof with embedded keys
    Aggregator->>Block: Merge Type-1 proofs into Type-2 proof
    Block->>Verifier: Block, aggregation bits, embedded-key proof
    Verifier->>Verifier: Derive expected keys from validator state
    Verifier->>Verifier: Cross-check messages, slots, and key sets
    Verifier->>Verifier: Verify recursive aggregate proof
Loading

Reviews (1): Last reviewed commit: "feat(crypto)!: track leanVM main (intern..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

  1. read_validator_keys still treats pubkey_hex as a syntax check only, then discards it before loading the secret keys. With the new API you can derive the public key (ValidatorSecretKey::public_key()), so a swapped or wrong key file will now be accepted at startup and only show up later as invalid attestations/proposals. That is a consensus-liveness regression and an avoidable operator-footgun. See bin/ethlambda/src/main.rs, bin/ethlambda/src/main.rs, and bin/ethlambda/src/main.rs.

  2. The loader also never enforces the dual-key invariant after parsing the files. The protocol docs in crates/common/types/src/state.rs require distinct attestation and proposal XMSS keys to avoid OTS reuse in the same slot, but bin/ethlambda/src/main.rs accepts whatever two secret-key files are present. If both entries resolve to the same key material, a validator that both attests and proposes in one slot will reuse the same XMSS leaf across two different messages. This should be rejected at startup by comparing the derived pubkeys for both roles.

Aside from that, the signature/aggregation migration looks internally consistent: the proof-verification paths now bind embedded pubkeys back to the expected validator sets, and the reaggregation path still sits behind full block-proof verification.

I couldn’t run cargo test here because the pinned toolchain resolution tries to write under the read-only ~/.rustup in this environment.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

…e branch

Tracking `branch = main` re-resolves on every fetch, so the build could
change under us. Pin the current main HEAD for reproducibility; the comment
notes it is main's tip and the rev can be bumped to move forward.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant